한 문장 요약
설계서에 들어가는 그림은 다섯 종류밖에 없다. 아래 판을 복붙해서 이름만 바꾼다.
0. 왜 draw.io 가 아니라 Mermaid 인가
항목
손그림 / 파워포인트
draw.io
Mermaid
처음 그리는 시간
빠름
느림 (박스 정렬)
빠름 (타이핑)
수정하는 시간
다시 그림
박스 옮기고 선 다시
한 줄 고침
버전 관리(git diff)
불가
불가(XML 덩어리)
텍스트 diff
리뷰어가 코멘트
불가
불가
줄 단위 가능
Obsidian / Notion
이미지 첨부
이미지 첨부
바로 렌더
배치가 마음에 안 들 때
마음대로
마음대로
못 건드림 ← 유일한 단점
판단 기준
설계 단계 그림은 반드시 바뀐다. 한 번 그리고 끝나는 그림은 없다.
그래서 “처음 그리는 시간”이 아니라 **“열 번 고치는 시간”**으로 골라야 한다.
→ 기본은 Mermaid. 배치까지 예쁘게 나와야 하는 최종 보고용 1장만 draw.io.
flowchart LR
A["그림이 필요하다"] --> B{"한 번 그리고<br/>끝인가?"}
B -- "아니오 (설계 중)" --> M["Mermaid"]
B -- "예 (대외 보고 1장)" --> D["draw.io / PPT"]
M --> C{"배치가<br/>너무 엉망인가?"}
C -- "아니오" --> M2["그대로 간다"]
C -- "예" --> D
**“언제”**가 중요하면 sequence, **“어느 쪽으로”**가 중요하면 flowchart,
**“무엇이 무엇을 가지나”**면 ER, **“지금 어떤 상태냐”**면 state.
2. flowchart — 함수 흐름 / 처리 절차
2-1. 복붙 판
flowchart TD
START(["요청 수신"]) --> V{"인자 검사<br/>NULL / 범위"}
V -- "실패" --> ERR["에러 리턴<br/>RET_INVALID_ARG"]
V -- "통과" --> ALLOC["자원 할당<br/>malloc / open"]
ALLOC --> A{"할당 성공?"}
A -- "아니오" --> ERR2["RET_NO_MEMORY"]
A -- "예" --> LOGIC["★ 비즈니스 로직"]
LOGIC --> R{"결과"}
R -- "정상" --> OK["결과 채우기<br/>ret = RET_OK"]
R -- "없음" --> EMPTY["ret = RET_NOT_FOUND"]
R -- "오류" --> FAILV["ret = RET_INTERNAL"]
OK --> CLEAN["CLEANUP<br/>free / close"]
EMPTY --> CLEAN
FAILV --> CLEAN
ERR2 --> CLEAN
CLEAN --> END(["return ret"])
ERR --> END
2-2. 문법 최소 세트
쓰고 싶은 것
문법
결과
방향
flowchart TD / LR
위→아래 / 왼→오른쪽
사각형 (처리)
A["처리"]
▭
마름모 (분기)
B{"조건?"}
◇
둥근 끝 (시작/끝)
C(["시작"])
⬭
원통 (DB/파일)
D[("DB")]
🛢
실선 화살표
A --> B
→
라벨 있는 화살표
A -- "성공" --> B
—성공→
점선
A -.-> B
⇢
굵은 선 (주 경로)
A ==> B
⇒
줄바꿈
A["첫줄<br/>둘째줄"]
2줄
대괄호 안은 항상 따옴표로 감쌀 것
A[검사(NULL)] → 깨진다. 괄호·콜론·쉼표·#·한글 조사 뒤 특수문자가 파서를 죽인다.
A["검사(NULL)"] ← 항상 이렇게
예외 없이 전부 ["..."] 로 쓰는 습관을 들이면 평생 안 깨진다.
2-3. 자주 만나는 렌더 문제
증상
원인
해결
그림이 통째로 안 나옴
노드 텍스트 특수문자
전부 ["..."] 로 감싸기
% 가 안 보임
%% 는 주석 문법
#37; 사용
" 를 넣고 싶다
따옴표 중첩
#quot; 사용
화살표 라벨이 깨짐
라벨에 특수문자
-- "텍스트" --> 로 따옴표
노드 ID 충돌
end 를 ID로 씀
end, graph, class 는 ID 금지 → END_, NODE_end
선이 너무 꼬임
노드가 15개 넘음
그림을 2장으로 쪼갠다
end 함정
A --> end ❌ 파서가 subgraph 종료로 오인 → 전체 붕괴
A --> END_NODE ✅
소문자 end 는 어디서도 노드 ID 로 쓰지 않는다.
2-4. {{ }} 채우기 판
flowchart TD
START(["{{ 진입 이벤트 }}"]) --> V{"{{ 검사 조건 }}"}
V -- "실패" --> ERR["{{ 에러 리턴코드 }}"]
V -- "통과" --> P1["{{ 처리 1 }}"]
P1 --> P2["{{ 처리 2 }}"]
P2 --> D{"{{ 분기 조건 }}"}
D -- "{{ 케이스 A }}" --> A1["{{ 처리 A }}"]
D -- "{{ 케이스 B }}" --> B1["{{ 처리 B }}"]
A1 --> CLEAN["CLEANUP"]
B1 --> CLEAN
ERR --> END_(["return"])
CLEAN --> END_
3. sequenceDiagram — Call Flow
**“누가 누구에게 무엇을 언제”**를 보여줄 때. 규격서의 Call Flow 는 100% 이것이다.
3-1. 복붙 판
sequenceDiagram
autonumber
participant C as Client
participant S as MyServer
participant W as Worker
participant D as DB
C->>S: Request (HTTP POST)
activate S
S->>S: 인자 검사 / 세션 할당
alt 검사 실패
S-->>C: 400 Bad Request
else 검사 통과
S->>W: enqueue(job)
activate W
W->>D: SELECT profile
activate D
D-->>W: rows
deactivate D
opt 캐시 갱신 필요
W->>W: hash_insert()
end
W-->>S: result
deactivate W
S-->>C: 201 Created
end
deactivate S
Note over S,W: 세션 버퍼는 응답 직후 반납
3-2. 문법 최소 세트
쓰고 싶은 것
문법
참가자 (별칭)
participant S as MyServer
요청 (실선 화살촉)
C->>S: 메시지
응답 (점선)
S-->>C: 응답
자기 호출
S->>S: 내부 처리
번호 자동 매김
autonumber (맨 위 1줄)
처리 중 표시
activate S … deactivate S
분기
alt 조건 / else 조건 / end
선택 (있을 수도)
opt 조건 … end
반복
loop N회 … end
병렬
par A / and B / end
설명 붙이기
Note over S,W: 텍스트
한쪽 설명
Note right of S: 텍스트
activate 는 꼭 쓰자
세로 막대가 생겨서 **“누가 아직 응답을 기다리는 중인지”**가 한눈에 보인다.
타임아웃 설계할 때 이 막대가 긴 구간이 바로 타이머를 걸어야 하는 구간이다.
alt / opt / loop 는 반드시 end 로 닫는다
안 닫으면 그림이 통째로 안 나온다. 중첩할 때는 들여쓰기로 짝을 눈으로 확인할 것.
3-3. 타임아웃·재시도를 그리는 관용구
sequenceDiagram
autonumber
participant A as Requester
participant B as Peer
A->>B: Request
activate A
Note right of A: 타이머 시작 (5s)
alt 정상 응답
B-->>A: 200 OK
Note right of A: 타이머 취소
else 타임아웃
Note right of A: 5s 경과
A->>A: 재시도 카운트++
A->>B: Request (retry 1/3)
B-->>A: 200 OK
else 3회 초과
A->>A: 세션 해제 + 알람 발생
end
deactivate A
3-4. {{ }} 채우기 판
sequenceDiagram
autonumber
participant A as {{ 송신 주체 }}
participant B as {{ 수신 주체 }}
participant C as {{ 저장소 }}
A->>B: {{ 요청 이름 }}
activate B
B->>B: {{ 검증 }}
alt {{ 정상 조건 }}
B->>C: {{ 조회/저장 }}
C-->>B: {{ 결과 }}
B-->>A: {{ 성공 응답코드 }}
else {{ 실패 조건 }}
B-->>A: {{ 실패 응답코드 }}
end
deactivate B
Note over A,B: {{ 주의사항 / 자원 해제 시점 }}
4. erDiagram — 데이터 구조
DB 테이블뿐 아니라 구조체 포함 관계를 그릴 때도 그대로 쓴다.
4-1. 복붙 판
erDiagram
NF_PROFILE ||--o{ NF_SERVICE : "has"
NF_PROFILE ||--o{ IPV4_ADDR : "has"
NF_SERVICE ||--o{ IP_ENDPOINT : "has"
NF_PROFILE }o--|| NF_TYPE : "is-a"
NF_PROFILE {
char sz_nf_instance_id "PK, UUID 36자"
int n_nf_type "enum, M"
int n_status "enum, M"
int n_heartbeat_sec "O, 기본 10"
int n_ipv4_cnt "0..8"
int has_capacity "Optional 플래그"
int n_capacity "O, 0..65535"
}
NF_SERVICE {
char sz_service_instance_id "PK"
char sz_service_name "M"
int n_ep_cnt "0..4"
}
IPV4_ADDR {
char sz_addr "15자 + NUL"
}
IP_ENDPOINT {
char sz_addr "15자 + NUL"
int n_port "1..65535"
}
NF_TYPE {
int n_code "enum"
char sz_name "문자열 표현"
}
4-2. 관계 기호 읽는 법
기호
의미
읽기
||--||
1 : 1
정확히 하나
||--o{
1 : 0..N
가장 많이 씀 (배열 멤버)
||--|{
1 : 1..N
최소 하나는 있어야 함
}o--o{
N : M
중간 테이블 필요
.. (실선 대신)
비식별 관계
NF ||..o{ LOG : ""
기호는 양쪽 끝을 각각 읽는다.||--o{ 는 왼쪽 “정확히 1개” + 오른쪽 “0개 이상”.
4-3. 구조체 → ERD 매핑 규칙
C 구조체
ERD
struct A { ... }
엔티티 A
int n_x;
속성 int n_x
B_t st_b; (값 포함, 1개)
A ||--|| B : "has"
B_t ast_b[8]; int n_cnt;
A ||--o{ B : "has (max 8)"
B_t *pst_b; (동적, 1개)
A ||--o| B : "has (nullable)"
int has_x; int n_x;
속성 2개 + 코멘트 "Optional"
조건부(C) / tagged union / anyOf
아래 4-3b
해시 키
첫 속성에 "PK" 코멘트
속성 타입에 특수문자 금지
ERD 속성은 타입 이름 "코멘트" 3칸 구조다. 타입 자리에 *, [], 공백이 들어가면 깨진다.
subgraph CORE 를 썼으면 CORE 라는 노드를 따로 만들 수 없다.
subgraph 는 XXX_G 처럼 접미사를 붙이는 습관을 들이면 안전하다.
7. 스타일 — 꼭 필요할 때만
기본 색으로 충분하다. 한 가지 경우만 예외: 에러 경로를 빨갛게 해서 눈에 띄게 할 때.
flowchart TD
A["요청"] --> B{"검증"}
B -- "통과" --> C["처리"]
B -- "실패" --> E["에러 응답"]
C --> D["정상 응답"]
classDef err fill:#ffe0e0,stroke:#d33,stroke-width:2px;
classDef ok fill:#e0ffe0,stroke:#3a3,stroke-width:2px;
class E err;
class D ok;
classDef {{ 클래스명 }} fill:#{{ 배경 }},stroke:#{{ 테두리 }},stroke-width:2px;
class {{ 노드ID1 }},{{ 노드ID2 }} {{ 클래스명 }};
색은 2개까지
빨강(에러) / 초록(정상) 이상 쓰면 아무도 안 본다. 색으로 의미를 만들지 말 것.
8. 깨졌을 때 30초 진단
flowchart TD
X["그림이 안 나온다"] --> A{"노드 텍스트가<br/>전부 따옴표?"}
A -- "아니오" --> A1["전부 [\"...\"] 로 감싼다"]
A -- "예" --> B{"alt/opt/loop/subgraph<br/>end 짝이 맞나?"}
B -- "아니오" --> B1["end 추가"]
B -- "예" --> C{"노드 ID 에<br/>end/graph/class 있나?"}
C -- "예" --> C1["ID 이름 변경"]
C -- "아니오" --> D{"% \" ( ) 같은<br/>특수문자 있나?"}
D -- "예" --> D1["#37; #quot; 로 치환"]
D -- "아니오" --> E["절반씩 지워가며<br/>범인 찾기"]
이스케이프가 필요한 문자
대체 표기
"
#quot;
%
#37;
#
#35;
()
따옴표 안이면 그대로 OK
<>
#lt;#gt;
줄바꿈
<br/>
범인 찾는 가장 빠른 방법
그림을 절반 지우고 렌더한다. 나오면 지운 쪽에 범인이 있다. 다시 절반. 4번이면 찾는다.